import {
  HttpException,
  HttpStatus,
  Inject,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { LoginUserDto } from './dto/login-user.dto';
import { UsersService } from '../users/users.service';
import { TokensService } from '../tokens/tokens.service';
import { JwtService } from '@nestjs/jwt';
import { compareSync } from 'bcrypt';
import { Cache } from 'cache-manager';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { EmailService } from './../email/email.service';
import { GLOBAL_CONSTANTS } from 'src/global/constants';
import { VerifyCodeDto } from './dto/verify-code.dto';
import { VerifyCodeType } from './authorize.enum';

@Injectable()
export class AuthorizeService {
  constructor(
    private readonly userService: UsersService,
    private readonly tokensService: TokensService,
    private readonly jwtService: JwtService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    private emailService: EmailService,
  ) {}

  /**
   * 登录
   * @param user
   * @returns
   */
  async login(user: LoginUserDto) {
    const db_user = await this.userService.findByUsername(user.username);
    if (!db_user) {
      throw new NotFoundException('该用户不存在');
    }
    /** 密码加密 + 盐 */
    if (!compareSync(user.password, db_user.password)) {
      throw new UnauthorizedException('Invalid credentials');
    }
    /**
     * JWT
     * - 头部（Header）
     * - 载荷（Payload）
     * - 签名（Signature）
     */
    const payload = { username: db_user.username, sub: db_user.userId };

    const token = await this.jwtService.signAsync(payload);

    /**
     * JWT Token 存数据库 + 本地缓存
     */
    this.cacheManager.set(`TOKEN-${db_user.userId}`, token);
    this.tokensService.setToken(db_user.userId, token);
    return {
      access_token: token,
    };
  }

  /**
   * 退出
   * @param token
   * @returns
   */
  async logout(token: string | undefined) {
    if (!token) {
      throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
    }
    // 解析token
    const payload = await this.jwtService.decode(token);
    // 删除本地缓存
    await this.cacheManager.del(`TOKEN-${payload.sub}`);
    // 删除数据库中的 token
    await this.tokensService.setToken(payload.sub, '');

    return null;
  }

  /**
   * 获取验证码
   */
  async getVerifyCode(verify: VerifyCodeDto) {
    const { verifyCodeType, email, phone } = verify;

    const phoneRegExp = /^1[3-9]\d{9}$/;
    const emailRegExp = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;

    if (
      verifyCodeType === VerifyCodeType.LOGIN_WITH_EMAIL ||
      verifyCodeType === VerifyCodeType.REGISTER_WITH_EMAIL ||
      verifyCodeType === VerifyCodeType.RESET_PASSWORD_WITH_EMAIL ||
      verifyCodeType === VerifyCodeType.CHANGE_EMAIL
    ) {
      if (!emailRegExp.test(email)) {
        throw new HttpException(
          'Invalid email',
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
      // 查询该邮箱是否已注册
      const user = await this.userService.findByEmail(email);
      if (!user) {
        throw new HttpException(
          'Email not exists',
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }

      // 发送验证码
      const code = Math.floor(Math.random() * 100000 + 100000);
      // 存入 Redis
      // 判断验证码是否过期
      const codeInCache = await this.cacheManager.get<string>(
        `EMAIL-VERIFY-CODE-${user.userId}`,
      );
      if (codeInCache) {
        // 发送邮件
        this.emailService.sendEmail(
          GLOBAL_CONSTANTS.MY_SELF_EMAIL,
          'Orange',
          `您的验证码还未过期，${user.username}, 验证码是：${codeInCache}，请勿泄露给他人，有效期5分钟`,
        );
        return null;
      }
      this.cacheManager.set(
        `EMAIL-VERIFY-CODE-${user.userId}`,
        code,
        300 * 1000,
      );
      // 发送邮件
      this.emailService.sendEmail(
        GLOBAL_CONSTANTS.MY_SELF_EMAIL,
        'Orange',
        `${user.username}, 您的验证码是：${code}，请勿泄露给他人，有效期5分钟`,
      );
    }

    if (
      verifyCodeType === VerifyCodeType.LOGIN_WITH_PHONE ||
      verifyCodeType === VerifyCodeType.REGISTER_WITH_PHONE ||
      verifyCodeType === VerifyCodeType.RESET_PASSWORD_WITH_PHONE ||
      verifyCodeType === VerifyCodeType.CHANGE_PHONE
    ) {
      if (!phoneRegExp.test(phone)) {
        throw new HttpException('Invalid phone', HttpStatus.OK);
      }
      // 查询该手机是否已注册
      const user = await this.userService.findByPhone(phone);
      if (!user) {
        throw new HttpException('Phone not exists', HttpStatus.OK);
      }
    }

    return null;
  }
}
